import {useEffect, useRef} from 'react';

const lerp = (a: number, b: number, n: number) => (1 - n) * a + n * b;
const map = (x: number, a: number, b: number, c: number, d: number) => (x - a) * (d - c) / (b - a) + c;

const calcWinsize = () => ({width: window.innerWidth, height: window.innerHeight});

const getCursorPos = (ev: MouseEvent) => ({x: ev.clientX, y: ev.clientY});

interface TrailOptions {
    perspective?: number;
    totalTrailElements?: number;
    valuesFromTo?: {
        x: [number, number];
        y: [number, number];
        rx: [number, number];
        ry: [number, number];
        rz: [number, number];
    };
    opacityChange?: boolean;
    amt?: (pos: number) => number;
    amtMain?: number;
}

const defaultOptions: Required<TrailOptions> = {
    perspective: 0,
    totalTrailElements: 5,
    valuesFromTo: {
        x: [-90, 90],
        y: [-90, 90],
        rx: [0, 0],
        ry: [0, 0],
        rz: [0, 0],
    },
    opacityChange: false,
    amt: (pos: number) => 0.02 * pos + 0.05,
    amtMain: 0.15,
};

const ImageTrail: React.FC<{ imageUrl: string; options?: TrailOptions }> = ({imageUrl = '', options = {}}) => {
    const mergedOptions = {...defaultOptions, ...options};
    const trailRef = useRef<HTMLDivElement>(null);
    const imgRefs = useRef<HTMLImageElement[]>([]);
    const imgTransforms = useRef<{ x: number; y: number; rx: number; ry: number; rz: number }[]>([]);
    const cursor = useRef({x: window.innerWidth / 2, y: window.innerHeight / 2});
    const winsize = useRef(calcWinsize());

    useEffect(() => {
        const handleResize = () => (winsize.current = calcWinsize());
        const handleMouseMove = (ev: MouseEvent) => (cursor.current = getCursorPos(ev));
        window.addEventListener('resize', handleResize);
        window.addEventListener('mousemove', handleMouseMove);

        return () => {
            window.removeEventListener('resize', handleResize);
            window.removeEventListener('mousemove', handleMouseMove);
        };
    }, []);

    useEffect(() => {
        imgTransforms.current = Array.from({length: mergedOptions.totalTrailElements}, () => ({
            x: 0,
            y: 0,
            rx: 0,
            ry: 0,
            rz: 0
        }));
        imgRefs.current = imgRefs.current.slice(0, mergedOptions.totalTrailElements);

        const render = () => {
            for (let i = 0; i < mergedOptions.totalTrailElements; ++i) {
                const amt = i < mergedOptions.totalTrailElements - 1 ? mergedOptions.amt(i) : mergedOptions.amtMain;
                const transform = imgTransforms.current[i];

                transform.x = lerp(transform.x, map(cursor.current.x, 0, winsize.current.width, mergedOptions.valuesFromTo.x[0], mergedOptions.valuesFromTo.x[1]), amt);
                transform.y = lerp(transform.y, map(cursor.current.y, 0, winsize.current.height, mergedOptions.valuesFromTo.y[0], mergedOptions.valuesFromTo.y[1]), amt);
                transform.rz = lerp(transform.rz, map(cursor.current.x, 0, winsize.current.width, mergedOptions.valuesFromTo.rz[0], mergedOptions.valuesFromTo.rz[1]), amt);
                transform.rx = mergedOptions.perspective ? lerp(transform.rx, map(cursor.current.y, 0, winsize.current.height, mergedOptions.valuesFromTo.rx[0], mergedOptions.valuesFromTo.rx[1]), amt) : 0;
                transform.ry = mergedOptions.perspective ? lerp(transform.ry, map(cursor.current.x, 0, winsize.current.width, mergedOptions.valuesFromTo.ry[0], mergedOptions.valuesFromTo.ry[1]), amt) : 0;

                const el = imgRefs.current[i];
                if (el) {
                    el.style.transform = `translateX(${transform.x}px) translateY(${transform.y}px) rotateX(${transform.rx}deg) rotateY(${transform.ry}deg) rotateZ(${transform.rz}deg)`;
                }
            }
            requestAnimationFrame(render);
        };
        requestAnimationFrame(render);
    }, [imageUrl, mergedOptions]);

    return (
        <div
            ref={trailRef}
            className="trail relative w-full h-full"
            style={mergedOptions.perspective ? {perspective: `${mergedOptions.perspective}px`} : {}}
        >
            {Array.from({length: mergedOptions.totalTrailElements}).map((_, i) => {
                const opacity = mergedOptions.opacityChange
                    ? i === mergedOptions.totalTrailElements - 1
                        ? 1
                        : 1 / mergedOptions.totalTrailElements * i + 1 / mergedOptions.totalTrailElements
                    : 1;
                return (
                    <img
                        key={i}
                        ref={el => {
                            if (el) imgRefs.current[i] = el;
                        }}
                        src={imageUrl}
                        className="trail__img absolute top-0 left-0 pointer-events-none"
                        style={{opacity}}
                        alt="trail"
                    />
                );
            })}
        </div>
    );
};

export default ImageTrail;
